package com.atguigu.tingshu.account.service.impl;
import java.util.Date;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;
import com.atguigu.tingshu.model.account.RechargeInfo;
import com.google.common.collect.Maps;

import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.account.UserAccount;
import com.atguigu.tingshu.model.account.UserAccountDetail;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {

	@Autowired
	private UserAccountMapper userAccountMapper;
	@Autowired
	private UserAccountDetailMapper userAccountDetailMapper;
	@Autowired
	private RedisTemplate<String,String> redisTemplate;


	@Override
	public void addUserAccount(String value) {
		UserAccount userAccount = new UserAccount();
		userAccount.setUserId(Long.parseLong(value));
		userAccount.setTotalAmount(new BigDecimal("0"));
		userAccount.setLockAmount(new BigDecimal("0"));
		userAccount.setAvailableAmount(new BigDecimal("0"));
		userAccount.setTotalIncomeAmount(new BigDecimal("0"));
		userAccount.setTotalPayAmount(new BigDecimal("0"));
		userAccountMapper.insert(userAccount);
	}

	@Override
	public BigDecimal getAvailableAmount() {
		// 构建查询条件对象
		LambdaQueryWrapper<UserAccount> userAccountLambdaQueryWrapper = new LambdaQueryWrapper<>();
		userAccountLambdaQueryWrapper.eq(UserAccount::getUserId , AuthContextHolder.getUserId()) ;

		// 进行查询
		UserAccount userAccount = userAccountMapper.selectOne(userAccountLambdaQueryWrapper);

		// 返回剩余金额
		return userAccount.getAvailableAmount() ;
	}

	@Override
	public AccountLockResultVo checkAndLock(AccountLockVo accountLockVo) {

		//获取交易号
		String orderNo = accountLockVo.getOrderNo();

		// 基于Redis保证接口的幂等性  ----> setnx命令
		String idempotentKey = "check:idempotent:" + orderNo ;
		String lockResultRedisKey ="account:lock:result:"+orderNo;
		//从Redis中根据key用setnx命令查找是否有数据，如果有则返回false说明重复提交
		Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(idempotentKey, orderNo, 5, TimeUnit.MINUTES);
		if (!aBoolean) {
			String lockResultJSON = redisTemplate.opsForValue().get(lockResultRedisKey);
			if (!StringUtils.isEmpty(lockResultJSON)) {
				AccountLockResultVo accountLockResultVo = JSON.parseObject(lockResultJSON, AccountLockResultVo.class);
				return accountLockResultVo;
			} else {
				// 还未计算出结果就再次请求
				throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_REPEAT);        // 封装的业务状态码为223
			}
		}

		// 获取accountLockVo对象的相关参数
		Long userId = accountLockVo.getUserId();
		String content = accountLockVo.getContent();
		BigDecimal amount = accountLockVo.getAmount();

		// 检查可用金额
		UserAccount userAccount = userAccountMapper.check(userId,amount);
		if (userAccount==null){
			redisTemplate.delete(idempotentKey);  // 删除幂等性key的保证
			throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_ERROR) ;   // 封装的业务状态码220
		}

		// 锁定可用金额
		int count = userAccountMapper.lock(userId , amount) ;  // int类型的返回值表示的就是影响数据行锁
		if(count == 0) {
			redisTemplate.delete(idempotentKey) ;			// 删除幂等性key的保证
			throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_ERROR) ;   // 封装的业务状态码220
		}

		// 记录账户变更的日志数据(user_account_detail)
		this.log(userId , "锁定:" + content ,
				SystemConstant.ACCOUNT_TRADE_TYPE_LOCK , amount , orderNo);

		// 构建响应结果数据
		AccountLockResultVo accountLockResultVo = new AccountLockResultVo();
		accountLockResultVo.setUserId(userId);
		accountLockResultVo.setAmount(amount);
		accountLockResultVo.setContent(content);
		redisTemplate.opsForValue().set(lockResultRedisKey ,JSON.toJSONString(accountLockResultVo),1, TimeUnit.HOURS);


		return accountLockResultVo;
	}

	@Override
	public void minusAccount(String orderNo) {
		// 保证接口的幂等性
		String idempotentKey = "account:minus:idempotent:" + orderNo ;
		Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(idempotentKey, orderNo, 5, TimeUnit.MINUTES);
		if(!aBoolean) return ;

		// 进行账户金额的扣减
		// 根据订单的编号从Redis中获取账户余额锁定结果数据
		String lockResultRedisKey = "account:lock:result:" + orderNo ;
		String json = redisTemplate.opsForValue().get(lockResultRedisKey);
		if(StringUtils.isEmpty(json))  return ;
		AccountLockResultVo accountLockResultVo = JSON.parseObject(json, AccountLockResultVo.class);
		int count = userAccountMapper.minusAccount(accountLockResultVo.getUserId() , accountLockResultVo.getAmount())  ;
		if(count == 0) {
			redisTemplate.delete(idempotentKey) ;
			throw new GuiguException(ResultCodeEnum.ACCOUNT_MINUSLOCK_ERROR);
		}

		// 记录账户变更的日志数据
		this.log(accountLockResultVo.getUserId() , "解锁: " + accountLockResultVo.getContent() ,
				SystemConstant.ACCOUNT_TRADE_TYPE_MINUS , accountLockResultVo.getAmount() , orderNo);

		// 从Redis中删除锁定账户金额的结果数据
		redisTemplate.delete(lockResultRedisKey) ;

	}

	@Override
	public void unLockAccount(String orderNo) {

		// 保证接口的幂等性
		String idempotentKey = "account:unlock:idempotent:" + orderNo ;
		Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(idempotentKey, orderNo, 5, TimeUnit.MINUTES);
		if(!aBoolean) return ;

		// 进行锁定金额的解锁
		String lockResultRedisKey = "account:lock:result:" + orderNo ;
		String json = redisTemplate.opsForValue().get(lockResultRedisKey);
		if(StringUtils.isEmpty(json)) return ;
		AccountLockResultVo accountLockResultVo = JSON.parseObject(json, AccountLockResultVo.class);
		Long userId = accountLockResultVo.getUserId();
		BigDecimal amount = accountLockResultVo.getAmount();
		int count = userAccountMapper.unLockAccount(userId , amount) ;
		if(count == 0) {
			redisTemplate.delete(idempotentKey) ;
			throw new GuiguException(ResultCodeEnum.ACCOUNT_UNLOCK_ERROR);
		}

		// 记录日志数据
		this.log(userId , "解锁: " + accountLockResultVo.getContent() ,
				SystemConstant.ACCOUNT_TRADE_TYPE_UNLOCK , amount , orderNo);


		// 从Redis中删除锁定账户金额的结果数据
		redisTemplate.delete(lockResultRedisKey) ;

	}

	@Override
	public void addUserAmount(RechargeInfo rechargeInfo) {

		//保证接口的幂等性
		String orderNo = rechargeInfo.getOrderNo();
		LambdaQueryWrapper<UserAccountDetail> lambdaQueryWrapper = new LambdaQueryWrapper<>();
		lambdaQueryWrapper.eq(UserAccountDetail::getOrderNo, orderNo);
		Long selectCount = userAccountDetailMapper.selectCount(lambdaQueryWrapper);
		if (selectCount > 0) return;

		Long userId = rechargeInfo.getUserId();
		BigDecimal rechargeAmount = rechargeInfo.getRechargeAmount();
		//更新账户金额
		userAccountMapper.addUserAmount( userId, rechargeAmount);

		//记录账户进行改变的日志数据
		this.log(userId, "充值", SystemConstant.ACCOUNT_TRADE_TYPE_DEPOSIT, rechargeAmount, rechargeInfo.getOrderNo());
	}

	private void log(Long userId , String title, String tradeType, BigDecimal amount , String orderNo) {

		// 构建账户明细日志对象
		UserAccountDetail userAccountDetail = new UserAccountDetail();
		userAccountDetail.setUserId(userId);
		userAccountDetail.setTitle(title);
		userAccountDetail.setTradeType(tradeType);
		userAccountDetail.setAmount(amount);
		userAccountDetail.setOrderNo(orderNo);
		userAccountDetailMapper.insert(userAccountDetail);

	}

}
